#include <system/d3d11/directX.h>
#include <system/win32/window_win32.h>
#include <fstream>
#include <array>
#include <system/platform.h>

//#include <graphics/d3d11/renderer_3d_d3d11.h>
//#include <graphics/d3d11/render_target_d3d11.h>

namespace fort
{
	D3D11::D3D11(HINSTANCE hinstance, UInt32 width, UInt32 height, bool fullscreen, bool vsync_enabled, bool test) :
		window(NULL),
		clock_last_frame(0),
		device(NULL),
		device_context(NULL),
		swap_chain(NULL),
		back_buffer(NULL),
		render_target_view(NULL),
		depth_stencil(NULL),
		depth_stencil_view(NULL),
		vsync_enabled(vsync_enabled)
	{
		HRESULT hresult = S_OK;

		setRenderTargetClearColour(Colour(0.4f, 0.525f, 0.7f, 1.0f)); 
		setDepthClearValue(1.0f);
		setStencilClearValue(0);

		//-- Create window
		window = new WindowWin32(hinstance, width, height, "Framework Application", fullscreen, true);
		setWidth(window->Width());
		setHeight(window->Height());

		//-- compile shaders
		CompileShaders();

		//-- Store the vsync setting.
		vsync_enabled = vsync_enabled;

		bool success = true;
		success = InitDeviceAndSwapChain(fullscreen);

	

		if(success)
		{
			//-- Create a render target view
			hresult = swap_chain->GetBuffer(0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&back_buffer);

			if(SUCCEEDED(hresult))
			{
				hresult = device->CreateRenderTargetView( back_buffer, NULL, &render_target_view);
				ReleaseNull(back_buffer);
			}

			//-- Create DepthStencil Buffer
			if(SUCCEEDED(hresult))
			{
				D3D11_TEXTURE2D_DESC desc_depth;
				desc_depth.Width = window->Width();
				desc_depth.Height = window->Height();
				desc_depth.MipLevels = 1;
				desc_depth.ArraySize = 1;
				desc_depth.Format = DXGI_FORMAT_D32_FLOAT;
				desc_depth.SampleDesc.Count = 1;
				desc_depth.SampleDesc.Quality = 0;
				desc_depth.Usage = D3D11_USAGE_DEFAULT;
				desc_depth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
				desc_depth.CPUAccessFlags = 0;
				desc_depth.MiscFlags = 0;
				hresult = device->CreateTexture2D( &desc_depth, NULL, &depth_stencil);

				if(SUCCEEDED(hresult))
				{
					D3D11_DEPTH_STENCIL_VIEW_DESC desc_DSV;
					ZeroMemory(&desc_DSV, sizeof(desc_DSV));
					desc_DSV.Format = desc_depth.Format;
					desc_DSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
					desc_DSV.Texture2D.MipSlice = 0;
					hresult = device->CreateDepthStencilView(depth_stencil, &desc_DSV, &this->depth_stencil_view);
				}
			}
		}

		if(SUCCEEDED(hresult))
		{
			//-- Bind the render and depth targets to the rendering pipeline 
			device_context->OMSetRenderTargets( 1, &render_target_view, depth_stencil_view);

			//-- Setup the viewport
			D3D11_VIEWPORT vp;
			ZeroMemory(&vp, sizeof(D3D11_VIEWPORT));

			vp.Width = static_cast<FLOAT>(window->Width());
			vp.Height = static_cast<FLOAT>(window->Height());
			vp.MinDepth = 0.0f;
			vp.MaxDepth = 1.0f;
			vp.TopLeftX = 0;
			vp.TopLeftY = 0;

			device_context->RSSetViewports( 1, &vp );
		}
		else
		{
			Release();
		}
	}

	D3D11::~D3D11()
	{
		Release();
	}

	void D3D11::Release()
	{
		ReleaseNull(depth_stencil_view);
		ReleaseNull(depth_stencil);
		ReleaseNull(render_target_view);
		ReleaseNull(back_buffer);
		ReleaseDeviceAndSwapChain();
		DeleteNull(window);
	}

	

	void D3D11::CompileShaders()
	{
		
	}
	bool D3D11::InitDeviceAndSwapChain(bool fullscreen)
	{
		IDXGIFactory* factory = NULL;
		IDXGIAdapter* adapter = NULL;
		IDXGIOutput* adapter_output = NULL;
		HRESULT hresult = S_OK;

		//-- Determine the resolution of the clients desktop screen.
		UInt32 screen_width  = GetSystemMetrics(SM_CXSCREEN);
		UInt32 screen_height = GetSystemMetrics(SM_CYSCREEN);

		UInt32 numerator = 0;
		UInt32 denominator = 1;

		//-- Create a DirectX graphics interface factory.
		hresult = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
		if(SUCCEEDED(hresult))
		{
			// Use the factory to create an adapter for the primary graphics interface (video card).
			hresult = factory->EnumAdapters(0, &adapter);
		}

		//-- Enumerate the primary adapter output (monitor).
		if(SUCCEEDED(hresult))
			hresult = adapter->EnumOutputs(0, &adapter_output);

		//-- Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
		UInt32 num_modes = 0;
		if(SUCCEEDED(hresult))
			hresult = adapter_output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &num_modes, NULL);

		if(SUCCEEDED(hresult))
		{
			//-- Create a list to hold all the possible display modes for this monitor/video card combination.
			DXGI_MODE_DESC* display_mode_list = new DXGI_MODE_DESC[num_modes];

			//-- Now fill the display mode list structures.
			hresult = adapter_output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &num_modes, display_mode_list);
			if(SUCCEEDED(hresult))
			{	
				//-- Now go through all the display modes and find the one that matches the screen width and height.
				//-- When a match is found store the numerator and denominator of the refresh rate for that monitor.
				for(UInt32 i=0; i<num_modes; i++)
				{
					if(display_mode_list[i].Width == (unsigned int)screen_width)
					{
						if(display_mode_list[i].Height == (unsigned int)screen_height)
						{
							numerator = display_mode_list[i].RefreshRate.Numerator;
							denominator = display_mode_list[i].RefreshRate.Denominator;
						}
					}
				}
			}
			//-- Release the display mode list.
			delete [] display_mode_list;
			display_mode_list = 0;
		}

		//-- Release the adapter output.
		ReleaseNull(adapter_output);
		ReleaseNull(adapter);
		ReleaseNull(factory);

		UINT create_device_flags = 0;
	#ifdef _DEBUG
		create_device_flags |= D3D11_CREATE_DEVICE_DEBUG;
	#endif

		D3D_DRIVER_TYPE driver_types[] =
		{
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_REFERENCE,
		};
		UINT num_driver_types = sizeof( driver_types ) / sizeof( driver_types[0] );

		DXGI_SWAP_CHAIN_DESC sd;
		ZeroMemory( &sd, sizeof( sd ) );
		sd.BufferCount = 1;
		sd.BufferDesc.Width = window->Width();
		sd.BufferDesc.Height = window->Height();
		sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		if(vsync_enabled)
		{
			sd.BufferDesc.RefreshRate.Numerator = numerator;
			sd.BufferDesc.RefreshRate.Denominator = denominator;
		}
		else
		{
			sd.BufferDesc.RefreshRate.Numerator = 0;
			sd.BufferDesc.RefreshRate.Denominator = 1;
		}
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		sd.OutputWindow = window->Hwnd();
		sd.SampleDesc.Count = 1;
		sd.SampleDesc.Quality = 0;
		sd.Windowed = fullscreen ? FALSE : TRUE;

		//-- Set the feature level to DirectX 11.
		D3D_FEATURE_LEVEL feature_levels[] =
		{
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
		};
		UINT num_feature_levels = sizeof( feature_levels ) / sizeof( feature_levels[0] );
	
		for( UINT driver_type_index = 0; driver_type_index < num_driver_types; driver_type_index++ )
		{
			driver_type = driver_types[driver_type_index];
			hresult = D3D11CreateDeviceAndSwapChain( NULL, this->driver_type, NULL, create_device_flags, feature_levels, num_feature_levels,
				D3D11_SDK_VERSION, &sd, &swap_chain, &device, NULL, &device_context);
			if( SUCCEEDED( hresult ) )
				break;
		}
    
		if SUCCEEDED( hresult )
			return true;
		else
		{
			Release();
			return false;
		}
	}

	void D3D11::ReleaseDeviceAndSwapChain()
	{
		if( device_context )
			device_context->ClearState();

		ReleaseNull(device_context);

		//-- Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
		if(swap_chain)
			swap_chain->SetFullscreenState(false, NULL);

		ReleaseNull(swap_chain);

		ReleaseNull(device);
	}

	void D3D11::PreRender()
	{
	    float clear_colour[4] = { 0.4f, 0.525f, 0.7f, 1.0f }; //red,green,blue,alpha
		device_context->ClearRenderTargetView( render_target_view, clear_colour );
		device_context->ClearDepthStencilView( depth_stencil_view, D3D11_CLEAR_DEPTH, 1.0f, 0 );
		Clear(true, true, true);
	}

	void D3D11::PostRender()
	{
		if(swap_chain)
			swap_chain->Present(vsync_enabled ? 1 : 0, 0);
	}

	void D3D11::Clear() const
	{
		Clear(true, true, true);
	}

	void D3D11::Clear(const bool clear_render_target_enabled, const bool clear_depth_buffer_enabled, const bool clear_stencil_buffer_enabled ) const
	{
		if(clear_render_target_enabled)
			device_context->ClearRenderTargetView( render_target_view, reinterpret_cast<const float*>(&renderTargetClearColour()) );

		UInt32 clear_flags = 0;
		if(clear_depth_buffer_enabled)
			clear_flags |= D3D11_CLEAR_DEPTH;

		if(clear_stencil_buffer_enabled)
			clear_flags |= D3D11_CLEAR_STENCIL;

		// if any of the flags have been set then
		// make the call to ClearRenderTargetView
		if(clear_flags)
			device_context->ClearDepthStencilView( depth_stencil_view, clear_flags, depthClearValue(), stencilClearValue() );
	}

	bool D3D11::Update()
	{
		// Initialize the message structure.
		MSG msg;
		ZeroMemory(&msg, sizeof(MSG));

		// Handle the windows messages.
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		return msg.message != WM_QUIT;
	}

	float D3D11::GetFrameTime()
	{
		// calculate the time between updates
		UInt64 clock, clock_frequency;
		if(clock_last_frame == 0)
			QueryPerformanceCounter((LARGE_INTEGER*)&clock_last_frame);

		QueryPerformanceCounter((LARGE_INTEGER*)&clock);
		QueryPerformanceFrequency((LARGE_INTEGER*)&clock_frequency);
		UInt32 cycles = ((UInt32)clock - clock_last_frame);
		clock_last_frame = clock;

		return (float)cycles / (float)clock_frequency;
	}

	/*class Mesh* D3D11::CreateMesh() const
	{
		return new Mesh();
	}*/

	/*Renderer3D* D3D11::CreateRenderer3D() const
	{
		return new Renderer3DD3D11(*this);
	}*/

	/*RenderTarget* D3D11::CreateRenderTarget(const Int32 width, const Int32 height) const
	{
		return new RenderTargetD3D11(*this, width, height);
	}


	void D3D11::BeginScene() const
	{
		ID3D11RenderTargetView* render_target_view;

		if(render_target())
		{
			RenderTargetD3D11* render_target_d3d11 = static_cast<RenderTargetD3D11*>(render_target());
			render_target_view = render_target_d3d11->render_target_view();
		}
		else
		{
			render_target_view = render_target_view_;
		}

		device_context_->OMSetRenderTargets( 1, &render_target_view, depth_stencil_view_ );
	}

	void D3D11::EndScene() const
	{
	}*/


}